package com.pes.arcww.controller;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
import com.google.common.base.Joiner;
import com.pes.arcww.form.Exams;
import com.pes.arcww.form.ExamsItems;
import com.pes.arcww.form.Items;
import com.pes.arcww.form.Profiles;
import com.pes.arcww.form.Questions;
import com.pes.arcww.form.Topics;
import com.pes.arcww.form.Users;
import com.pes.arcww.helper.Response_OUT;
import com.pes.arcww.service.ExamsService;
import com.pes.arcww.service.ProfilesService;
import com.pes.arcww.service.QuestionsService;
import com.pes.arcww.service.UsersService;

@Controller
@RequestMapping("/exams")
public class ExamsController {
	private static final Logger logger = LoggerFactory
			.getLogger(ExamsController.class);

	private static final String SRC_PATH_EXAMS = "/exams/";
	private static final Integer LEVEL_1 = 1;
	private static final Integer LEVEL_2 = 2;
	private static final Integer LEVEL_3 = 3;

	@Autowired(required = true)
	private ExamsService examService;
	@Autowired(required = true)
	private UsersService userService;
	@Autowired(required = true)
	private ProfilesService profileService;
	@Autowired(required = true)
	private QuestionsService questionService;

	private static UtilsApp utils = new UtilsApp();

	@RequestMapping(value = { "/" }, method = RequestMethod.GET)
	public @ResponseBody
	ModelAndView home(@ModelAttribute("model") ModelMap model,
			BindingResult result, HttpServletRequest request) {

		logger.debug("exams( model, result, request)");
		ModelAndView mav = new ModelAndView();

		try {
			model = utils.init(request, model, userService);
			if (request.getSession().getAttribute("logged_user") == null) {
				mav = new ModelAndView("redirect:/");
				return mav;
			} else {

				model.addAttribute("m_examsList", examService.listExams("", ""));

				mav = new ModelAndView(SRC_PATH_EXAMS + "home");
				mav.addObject("m_site", model);
			}
		} catch (Exception e) {
			logger.debug("exams( model, result, request) error: "
					+ e.getMessage());
			System.out.println(e.getMessage());
		}
		return mav;
	}

	/**
	 * Add
	 */
	@RequestMapping(value = { "/add" }, method = RequestMethod.GET)
	public @ResponseBody
	ModelAndView add(@ModelAttribute("model") ModelMap model,
			BindingResult result, HttpServletRequest request) {

		logger.debug("add( model, result, request)");
		ModelAndView mav = new ModelAndView();

		try {
			model = utils.init(request, model, userService);
			if (request.getSession().getAttribute("logged_user") == null) {
				mav = new ModelAndView("redirect:/");
				return mav;
			} else {
				model.addAttribute("m_profilesList",
						profileService.listProfiles("", ""));
				model.addAttribute("m_usersList", userService.listUsers("", ""));
				mav = new ModelAndView(SRC_PATH_EXAMS + "add");

				mav.addObject("m_site", model);
			}
		} catch (Exception e) {
			logger.debug("add( model, result, request) -" + e.getMessage());
		}
		return mav;
	}

	@RequestMapping(value = { "/add_input" }, method = RequestMethod.POST)
	public @ResponseBody
	Response_OUT add_input(@ModelAttribute("model") ModelMap model,
			BindingResult result, HttpServletRequest request,
			@RequestBody Map<String, Object> in) {

		Response_OUT out = new Response_OUT();
		logger.debug("add_input( model, result, request)");

		try {
			Exams exam = new Exams();
			exam.setProfile((Profiles) profileService.getProfile(Integer
					.parseInt(in.get("pro_id").toString())));
			exam.setUser((Users) userService
					.getUser(in.get("email").toString()));
			exam.setQuestionSize(Integer.parseInt(in.get("count").toString()));
			// ADD VALUES
			examService.addExam(exam);

			out = new Response_OUT("success", "000");
		} catch (Exception e) {
			logger.debug("add_input( model, result, request) -"
					+ e.getMessage());
			out = new Response_OUT("error", "999", e.getMessage());
		}
		return out;
	}

	/**
	 * Update
	 */
	@RequestMapping(value = { "/update/{id}" }, method = RequestMethod.GET)
	public @ResponseBody
	ModelAndView update(@ModelAttribute("model") ModelMap model,
			BindingResult result, HttpServletRequest request,
			@PathVariable Integer id) {

		logger.debug("update( model, result, request)");
		ModelAndView mav = new ModelAndView();

		try {
			model = utils.init(request, model, userService);
			if (request.getSession().getAttribute("logged_user") == null) {
				mav = new ModelAndView("redirect:/");
				return mav;
			} else {

				model.addAttribute("m_exam", (Exams) examService.getExam(id));
				model.addAttribute("m_profilesList", profileService.listProfiles("", ""));
				model.addAttribute("m_usersList", userService.listUsers("", ""));
				mav = new ModelAndView(SRC_PATH_EXAMS + "update");
				mav.addObject("m_site", model);
			}
		} catch (Exception e) {
			logger.debug("update( model, result, request) -" + e.getMessage());
		}
		return mav;
	}

	@RequestMapping(value = { "/update_input" }, method = RequestMethod.POST)
	public @ResponseBody
	Response_OUT update_input(@ModelAttribute("model") ModelMap model,
			BindingResult result, HttpServletRequest request,
			@RequestBody Map<String, Object> in) {

		logger.debug("update_input( model, result, request)");
		Response_OUT out = new Response_OUT();
		try {
			Exams exam = examService.getExam(Integer.parseInt(in.get("id")
					.toString()));
			// ADD VALUES
			exam.setProfile((Profiles) profileService.getProfile(Integer.parseInt(in.get("pro_id").toString())));
			exam.setUser((Users) userService.getUser(in.get("email").toString()));
			exam.setQuestionSize(Integer.parseInt(in.get("count").toString()));
			examService.updateExam(exam);
			out = new Response_OUT("success", "000");

		} catch (Exception e) {
			logger.debug("update_input( model, result, request) -"
					+ e.getMessage());
			out = new Response_OUT("error", "999", e.getMessage());
		}
		return out;
	}

	@RequestMapping(value = { "/delete" }, method = RequestMethod.POST)
	public @ResponseBody
	Response_OUT delete(@ModelAttribute("model") ModelMap model,
			BindingResult result, HttpServletRequest request,
			@RequestBody Map<String, Object> in) {

		logger.debug("delete( model, result, request)");
		Response_OUT out = new Response_OUT();
		try {

			examService.removeExam(Integer.parseInt(in.get("id").toString()));
			out = new Response_OUT("success", "000");

		} catch (Exception e) {
			logger.debug("delete( model, result, request) -" + e.getMessage());
			out = new Response_OUT("error", "999", e.getMessage());
		}
		return out;
	}

	@RequestMapping(value = { "/loadmore" }, method = RequestMethod.POST)
	public @ResponseBody
	ModelAndView loadmore(@ModelAttribute("model") ModelMap model,
			BindingResult result, HttpServletResponse response,
			HttpServletRequest request, @RequestBody Map<String, Object> in) {

		logger.debug("loadmore( model, result, request)");
		response.setContentType("application/json");

		ModelAndView mav = new ModelAndView();

		String filter = in.get("filter").toString();
		String value = in.get("content").toString();

		model = utils.init(request, model, userService);

		System.out.println("TYPE::" + value);

		if (value.equals("null")) {
			model.addAttribute("m_examsList", examService.listExams("", ""));
		} else {
			model.addAttribute("m_examsList",
					examService.listExams(filter, value));
		}

		model.addAttribute("in_filter", filter);
		model.addAttribute("in_content", value);
		model.addAttribute("in_count",
				Integer.parseInt(in.get("count").toString()));
		model.addAttribute("in_index",
				Integer.parseInt(in.get("index").toString()));

		mav = new ModelAndView(SRC_PATH_EXAMS + "list");
		mav.addObject("m_site", model);

		return mav;
	}

	/**
	 * Apply
	 */
	@RequestMapping(value = { "/apply/{id}" }, method = RequestMethod.GET)
	public @ResponseBody
	ModelAndView apply(@ModelAttribute("model") ModelMap model,
			BindingResult result, HttpServletRequest request,
			@PathVariable Integer id) {

		logger.debug("apply( model, result, request)");
		ModelAndView mav = new ModelAndView();

		try {
			model = utils.init(request, model, userService);
			if (request.getSession().getAttribute("logged_user") == null) {
				mav = new ModelAndView("redirect:/");
				return mav;
			} else {
				Exams exam = (Exams) examService.getExam(id);
				if(exam.getStarted() == false){
					exam.setStarted(true); //change flag / started.
					examService.updateExam(exam);
				}
				
				model = getViewQuestion(exam, request, model);
				mav = new ModelAndView(SRC_PATH_EXAMS + "apply");
				mav.addObject("m_site", model);
			}
		} catch (Exception e) {
			logger.debug("apply( model, result, request) -" + e.getMessage());
		}
		return mav;
	}

	@RequestMapping(value = { "/getQuestion" }, method = RequestMethod.POST)
	public @ResponseBody
	ModelAndView getQuestion(@ModelAttribute("model") ModelMap model,
			BindingResult result, HttpServletResponse response,
			HttpServletRequest request, @RequestBody Map<String, Object> in) {

		logger.debug("loadQuestion( model, result, request)");
		response.setContentType("application/json");

		
		ModelAndView mav = new ModelAndView();
		Integer id = Integer.parseInt(in.get("id").toString());
		Exams exam = examService.getExam(id);
		
		model = utils.init(request, model, userService);
		model = getViewQuestion(exam, request, model);
		model.addAttribute("m_totalQuestion", exam.getQuestionSize());
		model.addAttribute("m_get_view", "getQuestion");
		
		mav = new ModelAndView(SRC_PATH_EXAMS + "includes/views");
		mav.addObject("m_site", model);

		return mav;
	}


	@RequestMapping(value = { "/verifyQuestion" }, method = RequestMethod.POST)
	public @ResponseBody
	Response_OUT verifyQuestion(@ModelAttribute("model") ModelMap model,
			BindingResult result, HttpServletResponse response,
			HttpServletRequest request, @RequestBody Map<String, Object> in)
			throws NoSuchFieldException, SecurityException {

		logger.debug("verifyAnswer( model, result, request)");
		response.setContentType("application/json");

		Response_OUT out = new Response_OUT();

		try {
			@SuppressWarnings("unchecked")
			ArrayList<Object> list = (ArrayList<Object>) in.get("answers");
			Integer que_id = Integer.parseInt(in.get("que_id").toString());
			Integer exa_id = Integer.parseInt(in.get("exa_id").toString());


			// get question
			Exams exam = (Exams) examService.getExam(exa_id);
			Questions question = (Questions) questionService
					.getQuestion(que_id);
			ExamsItems cnt_item = new ExamsItems();
			Collection<Items> items = question.getItemsList();

			cnt_item.setExam(exam); // set exam.
			for (Iterator<Object> iterator = list.iterator(); iterator
					.hasNext();) {
				Object object = (Object) iterator.next();
				LinkedHashMap value = (LinkedHashMap) object;
				if ((Boolean) value.values().toArray()[1]) {
					Integer id = Integer.parseInt(value.values().toArray()[0]
							.toString());
					if (!verifyWithList(items, id)) {
						cnt_item.setFailed(true);
					} else {
						cnt_item.setFailed(false);
					}

					cnt_item.setSelected(true);
					
					Items item = (Items) questionService.getItem(id);
					cnt_item.setItem(item);
					examService.addExamsItems(exa_id, cnt_item);
				}
			}
			out = new Response_OUT(getCorrectAnswers(question), "000");

		} catch (Exception e) {
			logger.debug("verifyQuestion( model, result, request) -"
					+ e.getMessage());
			out = new Response_OUT("error", "999", e.getMessage());
		}
		return out;
	}

	@RequestMapping(value = { "/detail/{id}" }, method = RequestMethod.GET)
	public @ResponseBody
	ModelAndView detail(@ModelAttribute("model") ModelMap model,
			BindingResult result, HttpServletRequest request,
			@PathVariable Integer id) {

		logger.debug("detail( model, result, request, id)");
		ModelAndView mav = new ModelAndView();

		try {
			model = utils.init(request, model, userService);
			
			if (request.getSession().getAttribute("logged_user") == null) {
				mav = new ModelAndView("redirect:/");
				return mav;
			} else {
				Exams exam = (Exams) examService.getExam(id);
				if(exam.getQuestionSize() - getAnswered(exam).size() == 0){
					model = getExamInformation(exam, model);
				}else {// exam is still missing answers.
					model.addAttribute("m_invalid_exam", 4); // Access denied type.
				}
				mav = new ModelAndView(SRC_PATH_EXAMS + "detail");
				mav.addObject("m_site", model);
			}
		} catch (Exception e) {
			logger.debug("detail( model, result, request, id) -" + e.getMessage());
		}
		return mav;
	}
	
	private ModelMap getExamInformation(Exams exam, ModelMap model) {
		Set<Integer> questions = getAnswered(exam);

		Double totalLost = getTotalLostPts(questions, exam);
		Double totalWon = getTotalWonPts(questions, exam);
		Double totalPts = totalLost + totalWon;
		Double score  = Math.floor((totalWon/totalPts)*100);
		Set<Integer> answeredList = getAnswered(exam);
		Collection<Questions> questionsList = new ArrayList<Questions>();
		
		if(exam.getPending() == true){//resolve and save result
			exam.setScore(score.intValue());
			exam.setPending(false);
			exam.setFinished(true);
			if(exam.getScore()>70){
				exam.setApproved(true);
			}else{
				exam.setApproved(false);
			}
			//save exam
			examService.updateExam(exam);
		}
		
		for (Iterator<Integer> iterator = answeredList.iterator(); iterator.hasNext();) {
			Integer id = (Integer) iterator.next();
			questionsList.add(questionService.getQuestion(id));
		}
		
		model.addAttribute("m_questions", questionsList);
		model.addAttribute("m_exam", exam);
		model.addAttribute("m_pts_won", totalWon);
		model.addAttribute("m_pts_total", totalPts);
		model.addAttribute("m_pts_lost", totalLost);
		
		return model;
	}

	private Double getTotalWonPts(Set<Integer> questions, Exams exam) {
		Double pts = 0.0;
		for (Iterator<Integer> iterator = questions.iterator(); iterator.hasNext();) {
			Integer id = (Integer) iterator.next();
			Questions question = questionService.getQuestion(id);
			if(compareQuestion(exam, question)){
				pts = pts + question.getPoints();
			}
		}
		return pts;
	}

	private Double getTotalLostPts(Set<Integer> questions, Exams exam) {
		Double pts = 0.0;
		for (Iterator<Integer> iterator = questions.iterator(); iterator.hasNext();) {
			Integer id = (Integer) iterator.next();
			Questions question = questionService.getQuestion(id);
			if(!compareQuestion(exam, question)){
				pts = pts + question.getPoints();
			}
		}
		return pts;
	}

	
	private boolean compareQuestion(Exams exam, Questions question) {
		Collection<Items> items = question.getItemsList();
		Collection<ExamsItems> selectedItems = exam.getExamItemsList();
		Integer counterSelected = 0;
		Integer counterCorrect = 0;

		for (Iterator<Items> iterator = items.iterator(); iterator.hasNext();) {
			Items item = (Items) iterator.next();
			for (Iterator<ExamsItems> iterator2 = selectedItems.iterator(); iterator2.hasNext();) {
				ExamsItems selected = (ExamsItems) iterator2.next();
				if((selected.getItem().getId() == item.getId() && selected.getFailed() == false) && selected.getSelected() == true){
					counterSelected++; //asserted answer.
				}
			}
			
			//count correct answers. 
			if(item.getIsCorrect() == true){
				counterCorrect++;
			}
		}
		
		if(counterSelected == counterCorrect){
			return true;
		}
		return false;
	}

	private ModelMap getViewQuestion(Exams exam, HttpServletRequest request, ModelMap model) {
		
		Users user = userService.getUser(request.getSession().getAttribute("logged_user").toString());
		if (isOnMyList(user.getExamsList(), exam.getId())) {
			Integer remainingQuestions  = (exam.getQuestionSize() - getAnswered(exam).size());
			if(remainingQuestions != 0){
				model.addAttribute("remainingQuestions", remainingQuestions);
				model = getShuffledQuestion(exam, model);
			}else{
				model.addAttribute("m_complete", true);
			}
			model.addAttribute("m_exam", exam);
		} else {// exams is not on my queue, send to error page.
			model.addAttribute("m_invalid_exam", 1); // Access denied type.
		}
		return model;
	}
	/**
	 * Chech if is part of assigned exams.
	 * 
	 * @param exam list
	 * @param exam id
	 */
	public Boolean isOnMyList(Collection<Exams> list, Integer id) {
		for (Iterator<Exams> iterator = list.iterator(); iterator.hasNext();) {
			Exams exams = (Exams) iterator.next();
			if (exams.getId() == id)
				return true;
		}
		return false;
	}
	
	/**
	 * Get shuffled question according type of exam.
	 * 
	 * @param exam
	 * @param model
	 */
	private ModelMap getShuffledQuestion(Exams exam, ModelMap model) {
		Profiles profile = (Profiles) profileService.getProfile(exam
				.getProfile().getId());

		// create id list
		Collection<Topics> list = profile.getTopicsList();
		ArrayList<String> stringList = new ArrayList<String>();
		for (Iterator<Topics> iterator = list.iterator(); iterator.hasNext();) {
			Topics topics = (Topics) iterator.next();
			stringList.add(topics.getId().toString());
		}
		String where = Joiner.on(",").join(stringList); // join

		// find question by topic [0] and level
		Integer totalQuestions = exam.getQuestionSize();
		Integer rangeLevel1 = (int) Math.floor(totalQuestions / 3); // from 0 to
																	// totalQuestions/3
		Integer rangeLevel2 = (int) Math.floor((totalQuestions / 3) * 2); // from
																			// totalQuetions/3
																			// to
																			// (totalQuestions/3)*2
		Integer rangeLevel3 = (int) Math.floor(totalQuestions); // from
																// (totalQuestions/3)*2
																// to
																// totalQuestions

		Integer nextIndex = getAnswered(exam).size() + 1;
		Integer needed = 0;
		Integer level = 0;
		Collection<Questions> listQuestions = new ArrayList<Questions>();
		if (nextIndex > 0 && nextIndex <= rangeLevel1) { // level 1 easy
			level = LEVEL_1;
			needed = rangeLevel1;
		} else if (nextIndex > rangeLevel1 && nextIndex <= rangeLevel2) { // level
																			// 2
																			// medium
			level = LEVEL_2;
			needed = rangeLevel2;
		} else if (nextIndex > rangeLevel2 && nextIndex <= rangeLevel3) { // level
																			// 3
																			// hard
			level = LEVEL_3;
			needed = rangeLevel3;
		}
		listQuestions = (Collection<Questions>) questionService
				.listQuestionsByProfiles(where, level);

		// check if listQuestions.size() has enough questions to full-fit
		// current exam.
		if (listQuestions.size() >= needed) {

			Boolean check = false; // check if questions is already on current
									// exam.
			Set<Integer> currQuestionsList = getAnswered(exam);
			Questions question = new Questions();
			while (!check) {// end loop when check is equal to true.
				question = questionService.getQuestion(getRandomQuestion(
						listQuestions).getId());
				if (currQuestionsList.size() > 0) {
					check = true;
					for (Iterator<Integer> iterator = currQuestionsList
							.iterator(); iterator.hasNext();) {
						Integer id = iterator.next();
						if (id == question.getId()) {
							check = false; // stay in loop until reach diff
											// question.
						}
					}
				} else {// currQuestionsList is empty, any questions might be
						// added.
					check = true;
				}
			}
			model.addAttribute("m_question", question);
		} else {
			model.addAttribute("m_invalid_exam", 2);
		}

		return model;
	}

	private Questions getRandomQuestion(Collection<Questions> listQuestions) {
		Random randomGenerator = new Random();
		int index = randomGenerator.nextInt(listQuestions.size());
		Questions question = (Questions) listQuestions.toArray()[index];
		return question;
	}

	private Set<Integer> getAnswered(Exams exam) {
		Collection<ExamsItems> list = exam.getExamItemsList();
		Set<Integer> s = new LinkedHashSet<Integer>(); // string list;

		if (list.size() > 0) {
			for (Iterator<ExamsItems> iterator = list.iterator(); iterator
					.hasNext();) {
				ExamsItems examsItems = (ExamsItems) iterator.next();
				s.add(examsItems.getItem().getQuestion().getId());
			}
			s = new LinkedHashSet<Integer>(s); // removes duplicate questions
												// ID's
			return s;
		}
		return s;
	}

	private String getCorrectAnswers(Questions question) {
		Collection<Items> list = question.getItemsList();
		ArrayList<Integer> string = new ArrayList<Integer>();
		for (Iterator<Items> iterator = list.iterator(); iterator.hasNext();) {
			Items item = (Items) iterator.next();
			if(item.getIsCorrect()){
				string.add(item.getId());
			}
		}
		if(string.size() >1){
			return Joiner.on("|").join(string);
		}
		return string.get(0).toString();
		
	}

	private boolean verifyWithList(Collection<Items> items, Integer id) {
		for (Iterator<Items> iterator = items.iterator(); iterator.hasNext();) {
			Items item = (Items) iterator.next();
			if (item.getId() == id && item.getIsCorrect()) {
				return true;
			}
		}
		return false;
	}
}
